<?php

require_once "./RedisDB.php";
require_once "./Spider.php";

class Server
{
    private $serv = null;
    private $redis = null;
    private $spider = null;

    // 
    // 前两位0,0用于占位，因为设置worker_num=2，后四位表示对应的taskworker处于空闲状态
    // 可以通过 sum($taskworker_stat)>0 判断是否有taskworker处于空闲状态
    // private $taskworker_stat = [0,0,1,1,1,1];  

    public function __construct() {
        
        $this->serv = new Swoole\Server("127.0.0.1", 9501); 
        $this->redis = new RedisDB();
        $this->spider = new Spider();


        $this->serv->set([
            'worker_num'      => 4, //开启2个worker进程
            'task_worker_num' => 12, //开启4个task进程
        ]);

        $this->serv->on('Start', [$this, 'onStart']);
        $this->serv->on('Connect', [$this, 'onConnect']);
        $this->serv->on("Receive", [$this, 'onReceive']);
        $this->serv->on("Close", [$this, 'onClose']);
        $this->serv->on("Task", [$this, 'onTask']); 
        $this->serv->on("Finish", [$this, 'onFinish']);

        $this->serv->start();
    }

    public function onStart($serv) {
        echo "############################ onStart ############################".PHP_EOL;
        echo "SWOOLE ".SWOOLE_VERSION . " 服务已启动".PHP_EOL;
        echo "master_pid: {$serv->master_pid}".PHP_EOL;
        echo "manager_pid: {$serv->manager_pid}".PHP_EOL;
        echo "===============================================================".PHP_EOL.PHP_EOL;
    }

    public function onConnect($serv, $fd) {
        echo "########################### onConnect ###########################".PHP_EOL;
        echo "客户端:".$fd." 已连接".PHP_EOL;
        echo "===============================================================".PHP_EOL.PHP_EOL;
    }

    public function onReceive($serv, $fd, $from_id, $data) {
        echo "########################### onReceive ###########################".PHP_EOL;
        echo "worker_pid: {$serv->worker_pid}".PHP_EOL;
        echo "客户端:{$fd} 发来的了任务".PHP_EOL;

        $serv->send($fd, "服务端已收到任务，请耐心等待....");

        $data_arr = json_decode($data, true);

        $rs = $serv->task($data_arr['uid']);
        if ($rs === false) {
            echo "【投递任务{$data_arr['uid']}：失败】".PHP_EOL;
        } else {
            echo "【投递任务{$data_arr['uid']}：成功】".PHP_EOL;
        }

        echo "===============================================================".PHP_EOL.PHP_EOL;
    }

    public function onTask($serv, $task_id, $from_id, $data) {
        echo "########################### onTask ###########################".PHP_EOL;
        echo "【代号：{$serv->worker_id}】【当前进程：{$serv->worker_pid}】【任务编号：task_id={$task_id}】".PHP_EOL;
        
        // 设置当前taskworker为空闲状态
        // $this->taskworker_stat[$serv->worker_id] = 0;

        // 收到的data就是uid
        $uid = $data;
        // 设置uid状态成功标志
        $set_key_res = true; // 设置状态失败 $set_key_res=false，防止某个uid常驻任务池中
        
        // 
        // 根据任务的uid状态，分配任务
        $task_status = $this->redis->getKeyStatus($uid);
        switch($task_status){
            case "0":
                // 没获取粉丝和关注列表，也没获取用户信息
                // 为了平衡生产与消费，采用概率分配
                // 这个概率可以通过测试和推导得出，但最终还是根据在这实际测试来定
                // $rand_num = rand(1,13);
                if($task_id == 0){
                    $list = $this->spider->crawlList($uid);    // 概率：1/13
                    $this->redis->setKeyStatus($uid, 'list');  // 设置状态为已获取粉丝和关注列表
                }else{
                    $info = $this->spider->crawlInfo($uid);   // 概率：12/13
                    $this->redis->setKeyStatus($uid, 'info');  // 设置状态为已获取粉丝和关注列表
                }
            break;
            case "1":
                // 已获取粉丝和关注列表，需要获取用户信息
                $info = $this->spider->crawlInfo($uid);    // 
                $this->redis->setKeyStatus($uid, 'info');  // 设置状态为已获取粉丝和关注列表
            break;
            case "2":
                // 已获取用户信息，需要获取粉丝和关注列表

                // 
                // $relu = rand(1,100);
                // if($relu > 40) break;

                $list = $this->spider->crawlList($uid);    // 
                $this->redis->setKeyStatus($uid, 'list');  // 设置状态为已获取粉丝和关注列表
                
                
                
            break;
            case "3":
                // 已获取粉丝和关注列表，已获取用户信息，需要将用户信息 从redis存入MySQL
                // 考虑到cup的占用，将这部分单独做一个模块，等爬完数据再进行操作
            break;
            default:
                // 没有什么bug是一行注释解决不了的，~_~ <_> >_<
            break;
        }

        // 在finish前，设置当前taskworker为空闲状态
        // $this->taskworker_stat[$serv->worker_id] = 0;

        if(isset($list)){
            // 完成 crawlList 
            $serv->finish(json_encode(array('type'=>'list','uid'=>$uid,'list'=>$list)));
        }else if(isset($info)){
            // 完成 crawlInfo
            $serv->finish(json_encode(array('type'=>'info','uid'=>$uid,'info'=>$info)));
        }else{
            // 走了个过程，
            $serv->finish(json_encode(array('type'=>'pass','uid'=>$uid)));
        }

        echo "===============================================================".PHP_EOL.PHP_EOL;
    }

    public function onFinish($serv,$task_id, $data) {
        echo "########################### onFinish ###########################".PHP_EOL;
        echo "【已完成任务：{$task_id}】".PHP_EOL;

        $res = json_decode($data, true);
        $uid_status = $this->redis->getKeyStatus($res['uid']);

        if($res['type'] == 'list'){
            if($uid_status == "1"){
                // 已获取粉丝和关注列表，但还没获取该用户信息，需要放回任务池

                // 当前没有空闲的taskworker，等待有空闲的taskworker再投放任务
                // while(array_sum($this->taskworker_stat) == 0){
                //     Swoole\Coroutine\System::sleep(0.2);
                // }
                $rs = $serv->task($res['uid']);
                if ($rs === false) {
                    echo "【再次投递任务{$res['uid']}：失败】".PHP_EOL;
                } else {
                    echo "【再次投递任务{$res['uid']}：成功】".PHP_EOL;
                }
            }

            // 将获取到的列表的用户uid，放到任务池
            if(!empty($res['list'])){
                foreach($res['list'] as $index => $uid){

                    // 防止一次投递过多任务，造成生产速度远大于消费速度
                    if($index >= 8) break;
                    
                    $stat = $this->redis->getKeyStatus($uid);
                    if($stat != "3"){  // 爬虫可能会爬到以前爬到过的uid
                        // 当前没有空闲的taskworker，等待有空闲的taskworker再投放任务
                        // while(array_sum($this->taskworker_stat) == 0){
                        //     Swoole\Coroutine\System::sleep(0.2);
                        // }
                        $rs = $serv->task($uid);
                        if ($rs === false) {
                            echo "【投递任务{$uid}：失败】".PHP_EOL;
                        } else {
                            echo "【投递任务{$uid}：成功】".PHP_EOL;
                        }
                    }   
                }
            }
        }else if($res['type'] == 'info'){
            if($uid_status == "2"){
                // 已获取该用户信息，但还没获取粉丝和关注列表，需要放回任务池

                // 当前没有空闲的taskworker，等待有空闲的taskworker再投放任务
                // while(array_sum($this->taskworker_stat) == 0){
                //     Swoole\Coroutine\System::sleep(0.2);
                // }
                $rs = $serv->task($res['uid']);
                if ($rs === false) {
                    echo "【再次投递任务{$res['uid']}：失败】".PHP_EOL;
                } else {
                    echo "【再次投递任务{$res['uid']}：成功】".PHP_EOL;
                }

            }
            // 将用户信息保存到redis
            $rs = $this->redis->saveInfo($res['uid'],$res['info']);
            if($rs){
                echo "【保存信息{$res['uid']}：成功】".PHP_EOL;
            }else{
                
                echo "【保存信息{$res['uid']}：失败（可能该用户信息已存在）】".PHP_EOL;
            }
        }

        echo "===============================================================".PHP_EOL.PHP_EOL;
    }

    public function onClose($serv, $fd) {
        echo "Client Close.".PHP_EOL;
    }
}

$server = new Server();
